<template>
  <div>
    <van-search v-model="searchValue" shape="round" background="red" show-action placeholder="小米集团发公告">
      <template #action>
        <div style="text-align: center">
          <van-icon name="add" size="22" color="#fff" />
          <div style="font-size: 12px; color: #fff; transform: scale(0.8, 0.8)">发布</div>
        </div>
      </template>
    </van-search>
    <div style="margin-top: 46px; padding-left: 15px; padding-right: 15px; overflow: auto">
      <h1>APP 首页</h1>
      <h2>一、页面进出场动画</h2>
      <h3>动效高仿《今日头条》APP，实现：</h3>
      <ol class="ol">
        <li>1. 模仿《今日头条》，Z轴靠下的页面不动，Z轴靠上的页面实现进出场动画</li>
        <li>2. 不动的页面使用static定位，避免脱离正常流</li>
        <li>3. 支持记忆访问过的页面的滚动条位置，前进和回退时均可自动滚动到记忆位置</li>
        <li>4. 支持设定“特定页面至特定页面”的保持、不保持滚动条</li>
        <li>5. 保持滚动条位置与keep-alive无关，不缓存的页面也支持保持滚动条位置</li>
      </ol>
      <h3>方案</h3>
      <p>项目内大多数文件都有涉及，看注释即可。</p>
      <h3>测试</h3>
      <p>1. 测试保持滚动位置：</p>
      <p>
        首页 -> List -> Article1 ->
        Article2，再依次回退。你可以多做几遍。可以看到，除正文页外，滚动位置全部保持。注意先任意滚动页面再点击按钮。其中正文页由于ajax加载内容，所以不缓存，也不保存滚动条位置。
      </p>
      <button @click="$router.push({ name: 'List', params: { keepScrollY: true } })">去List页</button>
      <p>让新页面保持滚动位置的办法是加params.keepScrollY，比如：</p>
      <van-notice-bar
        wrapable
        :scrollable="false"
        text="this.$router.push({ name: 'List', params: { keepScrollY: true } })"
      />
      <p>还可以同时加query：</p>
      <van-notice-bar
        wrapable
        :scrollable="false"
        text="this.$router.push({ name: 'List', query: {id: 12345} params: { keepScrollY: true } })"
      />
      <p>提醒：由于要传params，所以不可使用path:'/list'。</p>
      <p>
        2. 测试不保持滚动位置：首页 -> List -> Article1 ->
        Article2，再依次回退，可以看到滚动位置全部没有保持。注意先任意滚动页面再点击按钮。
      </p>
      <p>无需传params.keepScrollY。</p>
      <p><button @click="$router.push({ name: 'List' })">去List页</button></p>

      <h2>二、常规keep-alive</h2>
      <h3>要求</h3>
      <p>支持使用相同组件的2个页面切换且保持各自缓存。</p>
      <h3>原理</h3>
      <p>1. &lt;router-view&gt;&lt;/router-view&gt;必须设:key="$route.fullPath"</p>
      <p>2. 在路由表的meta设keepAlive: true。</p>
      <p>3. 递归查询路由表里所有设了keepAlive的路由，保存到cachedViews。</p>
      <p>给&lt;keep-alive&gt;&lt;/keep-alive&gt;加 :include="$store.state.cachedViews" 即可。</p>
      <p>以上都已在本项目实现，不再过多解释。</p>
      <h3>测试</h3>
      <p>不提供专门测试，因为没必要，可以借用下一章的测试。本项目中所有页面都设了keep-alive。</p>

      <h2>三、动态keep-alive</h2>
      <p>
        所谓动态keep-alive，是对keep-alive的更高要求，即：从某些页进入目标页时，保持keep-alive，从另一些页进入目标页时，不保持keep-alive。
      </p>
      <p>使用场景很多，比如：从首页进入用户注册页，需要填用户信息，其中填写地址需要进入一个专门填写地址的地址页。</p>
      <p>从首页进入注册页，要求注册页是全新的。从地址页进入注册页，要求保留已填信息。</p>
      <h3>测试</h3>
      <p>现在把List页假想成注册页，把Article1假想成地址页。去本页上方点击按钮测试即可。</p>
      <p>1. 当首页进入List页，会永远看到全新页面。此时在List页的输入框随意输入字符。</p>
      <p>2. 当Article1返回List页，会永远看到那些字符。</p>
      <p>3. 再返回首页，当首页第二次进入List页，依然看到全新页面。</p>
      <h3>方案</h3>
      <ol class="ol">
        <li>1. 像常规一样，在路由表给注册页设keepAlive:true。</li>
        <li>2. 给注册页添加这些代码即可：</li>
      </ol>
      <pre>import store from '@/store';</pre>
      <pre>
  beforeRouteEnter(to, from, next) {
    if (from.name === 'Home') {
      store.commit('ADD_CACHED_VIEWS', 'List');
    }
    next();
  },
  beforeRouteLeave(to, from, next) {
    if (to.name === 'Home') {
      this.$store.commit('DEL_CACHED_VIEWS', 'List');
    }
    next();
  },</pre
      >
      <h3>原理</h3>
      <p>
        按道理说，原理应该是A->B之前，给B抹掉keepAlive，B->C之前，给B添加keepAlive。但是，根据此原理操作的话，第一遍
        A->B->C->B->A 没有问题，第二遍就有问题了，原因暂未知。所以现在采用的原理是逆向思维，而且代码反而是最简单的：
      </p>
      <p>
        现在A依然希望抹掉B的keepAlive，但是，A->B之前反而让B设keepAlive，之后，只在B返回A之前给B清掉keepAlive即可，这样下次A到B依然是全新的B。
      </p>
      <p>因为B到了A意味着B放弃了自身的缓存，所以B丢掉数据是逻辑合理的。</p>
      <p>同时，可以不用管C。</p>
      <p>
        这就好比：
        一个人在家招待的如果是普通客人（比如C），那么家里保持乱糟糟即可，什么都不用操作；唯独给贵客（比如A）发邀请函之前要打扫干净房间，那么贵客来之后看到的始终是全新的房间。同时贵客告诉你两件事：
      </p>
      <p>1. 我来你家之前，你家必须是全新的。</p>
      <p>2. 但是，无论我在你家霍霍成什么样，你就要保持什么样，一律缓存住，因为我要让别人看我霍霍的结果。</p>
      <p>所以只需关注贵客即可。</p>

      <h2>四、处理下级页面做为着陆页的情况</h2>
      <p>下级页面也就是非首页的所有页面，也就是浏览器访问Web APP的第一页页面不是首页。这时要实现：</p>
      <ol class="ol">
        <li>1. 虽然打开页面就已经进行了一次路由跳转，但是不应发生动画。</li>
        <li>2. 点击页面导航条回退按钮，不应该执行go(-1)，因为执行的话就跳出了Web APP，应该是push到首页。</li>
      </ol>
      <h3>方案</h3>
      <p>涉及2个文件：App.vue和/components/NavBar.vue，看注释即可，不多解释。</p>
      <h2>五、实现：“第一次回退先关弹层”，“第二次回退真正回退”</h2>
      <h3>需求</h3>
      <p>当有大面积的甚至全屏的弹层出现时，用户可能希望通过点击手机“回退”键关闭弹层，而不是真正路由回退。</p>
      <p>而且，弹层是fixed元素，如果不关掉就路由回退，还需要专门给元素添加translate，对开发者属于心智负担。</p>
      <p>这里专门解释一下这个操作：</p>
      <p>fixed元素有一个特点是：祖先任一元素发生translate动画，则fixed就以该祖先为定位祖先。</p>
      <p>这就发生一个意外问题，动画过程中fixed元素不再基于视口定位，由于有滚动条的关系，fixed元素会跑到上方去。</p>
      <p>
        解决方案是，不给fixed元素的祖先设置translate，而是给fixed元素本身设置translate，同时也给其他元素设置translate。现在等于是分别动画，并行发生。
      </p>
      <h3>原理</h3>
      <p>
        利用beforeRouteLeave钩子的next(false)，如果判断出有未关闭的弹层，则关闭弹层，然后next(false)。具体见List页面源码。
      </p>
      <h3>适用范围</h3>
      <p>
        适用于页面不是着陆页的情况。如果页面是着陆页，则点击回退会直接退到空白页或其他网站。常规没有办法解决，本项目认为没有必要强行解决，所以不再考虑这个情况。
      </p>
      <h3>测试</h3>
      <p>1. 进入List页，点击里面的按钮，打开弹出层。</p>
      <p><button @click="$router.push({ name: 'List', params: { keepScrollY: true } })">去List页</button></p>
      <p>2. 接着点击浏览器后退按钮。你会发现浏览器并没有回退，只是层消失了。</p>
      <div style="height: 100px"></div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Index',
  data() {
    return {
      searchValue: '',
    };
  },
};
</script>

<style lang="scss">
.van-search {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;

  .van-search__action {
    line-height: 1;
  }
}
p,
.ol {
  font-size: 14px;
  line-height: 1.6;
}
</style>
